import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.regex.PatternSyntaxException;

/**
 * This class contains functions for manipulating Text files
 * @author Tzafrir
 *
 */
public class StringProcess
{
	private static StringProcess handle;
	private static final String TEMP_TAG="-temp";
	
	static private class AddAfterLine implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Add after line";
		}

		public String replace(String Line,String searchPattern, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return Line+EOL+newText;
		}
	}
	
	static private class AddBeforeLine implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Add before line";
		}

		public String replace(String Line, String regex, String newText,String EOL)
		{
			Log.debug("Patching line: "+Line);
			return newText+EOL+Line;
		}
	}
	
	static private class DelLine implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Delete Line";
		}

		public String replace(String Line,String searchPattern, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return null;
		}
	}
	
	static private class changeNonQuotedFieldValue implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Change Field Value no quotes";
		}
		/**
		 * THIS METHOD SUITS FOR ONE FIELD PER LINE FILES
		 */
		public String replace(String Line,String fieldName, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return Line.replaceAll(fieldName+"=.*", fieldName+"="+newText); //this will replace the entire line.
		}
	}
	
	static private class changeQuotedFieldValue implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Change quoted Field Value ";
		}

		public String replace(String Line,String fieldName, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return Line.replaceAll(fieldName+"=\".*?\"", fieldName+"=\""+newText+"\"");
		}
	}
	
	static private class changeTagValue implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "Change tag value";
		}

		public String replace(String Line,String fieldName, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return Line.replaceAll(">.*<", ">"+newText+"<");
		}
	}
	
	static private class replaceAll implements StringProccessMethod
	{
		public String getMethodName()
		{
			return "replace all";
		}

		public String replace(String Line,String searchPattern, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			return Line.replaceAll(searchPattern,newText);
		}
	}
	
	static private class replaceFirst implements StringProccessMethod
	{
		private boolean found=false;
		public String getMethodName()
		{
			return "replace first";
		}

		public String replace(String Line,String searchPattern, String newText, String EOL)
		{
			Log.debug("Patching line: "+Line);
			if (found)
				return Line;
			String ret=Line.replaceAll(searchPattern,newText);
			
			if (!ret.equals(Line))
				found=true;
			return ret;
		}
	}
	
	static private interface StringProccessMethod
	{
		public String getMethodName();
		
		public String replace(String Line,String regex, String newText, String EOL);
	}
	
	/**
	 * Get instance of the one and only String process
	 * @return
	 */
	public static StringProcess getInstance()
	{
		if (handle==null)
			handle=new StringProcess();
		return handle;
	}
	
	/**
	 * this functions add the given line after the line recognized by linePattern 
	 * @param filePath - file name to work on
	 * @param LinePattern - pattern to recognize line.
	 * @param newText - new line to add
	 * @return Number of lines replaced
	 */
	public int addAfterLine(String filePath, String LinePattern, String newText)
	{
		StringProccessMethod method=new AddAfterLine();
		return PatchFile(filePath,LinePattern,null,newText,method);
	}

	/**
	 * this functions add the given line before line recognized by linePattern 
	 * @param filePath - file name to work on
	 * @param LinePattern - pattern to recognize line.
	 * @param newText - new line to add
	 * @return Number of lines replaced
	 */
	public int addBeforeLine(String filePath, String LinePattern, String newText)
	{
		StringProccessMethod method=new AddBeforeLine();
		return PatchFile(filePath,LinePattern,null,newText,method);
	}
	/**
	 * Deletes a given line from file
	 * @param filePath - file name. relative or absolute
	 * @param LinePattern - pattern by which line will be recognized
	 * @return number of lines delete
	 */
	public int delLine(String filePath, String LinePattern)
	{
		StringProccessMethod method=new DelLine();
		return PatchFile(filePath,LinePattern,null,null,method);
	}
	
	/**
	 * changes all non quoted fields values in the file to given value
	 * @param filePath - file path to work on
	 * @param fieldName - field to patch
	 * @param newText - new field value
	 * @return number of lines patched
	 */
	public int changeNonQuotedFieldValue(String filePath, String fieldName, String newText)
	{
		StringProccessMethod method=new changeNonQuotedFieldValue();
		return PatchFile(filePath,null,fieldName,newText,method);
	}
	
	/**
	 * changes non quoted fields value in given line of the file to given value
	 * @param filePath - file path to work on
	 * @param linePattern - pattern by which line can be identified
	 * @param fieldName - field to patch
	 * @param newText - new field value
	 * @return number of lines patched
	 */
	public int changeNonQuotedFieldValueinLine(String filePath, String linePattern,String fieldName, String newText)
	{
		StringProccessMethod method=new changeNonQuotedFieldValue();
		return PatchFile(filePath,linePattern,fieldName,newText,method);
	}
	
	/**
	 * changes all quoted fields values in the file to given value
	 * @param filePath - file path to work on
	 * @param fieldName - field to patch
	 * @param newText - new field value
	 * @return number of lines patched
	 */
	public int changeQuotedFieldValue(String filePath, String fieldName, String newText)
	{
		StringProccessMethod method=new changeQuotedFieldValue();
		return PatchFile(filePath,null,fieldName,newText,method);
	}
	
	/**
	 * changes quoted fields value in given line of the file to given value
	 * @param filePath - file path to work on
	 * @param linePattern - pattern by which line can be identified
	 * @param fieldName - field to patch
	 * @param newText - new field value
	 * @return number of lines patched
	 */
	public int changeQuotedFieldValueinLine(String filePath, String linePattern,String fieldName, String newText)
	{
		StringProccessMethod method=new changeQuotedFieldValue();
		return PatchFile(filePath,linePattern,fieldName,newText,method);
	}
	
	/**
	 * change an xml tag value. |TAG| VALUE_TO_BE_REPLACED |/TAG|
	 * @param filePath -file path to patch
	 * @param linePattern - line pattern to recognize pattern by
	 * @param newText - new text for the tag value
	 * @return number of lines patched
	 */
	public int changeTagValue(String filePath, String linePattern, String newText)
	{
		StringProccessMethod method=new changeTagValue();
		return PatchFile(filePath,linePattern,null,newText,method);
	}
	
	/**
	 * replace all occurrences of a given string pattern with a given string
	 * @param filePath -file path to patch
	 * @param searchPattern - pattern which will be replaced
	 * @param newText - new string
	 * @return number of lines patched
	 */
	public int replaceAll(String filePath, String searchPattern, String newText)
	{
		StringProccessMethod method=new replaceAll();
		return PatchFile(filePath,null,searchPattern,newText,method);
	}
	
	/**
	 * replace First occurrence of a given string pattern with a given string
	 * @param filePath -file path to patch
	 * @param searchPattern - pattern which will be replaced
	 * @param newText - new string
	 * @return number of lines patched
	 */
	public int replaceFirst(String filePath, String searchPattern, String newText)
	{
		StringProccessMethod method=new replaceFirst();
		return PatchFile(filePath,null,searchPattern,newText,method);
	}
	
	/**
	 * replace all occurrences of a given string pattern with a given string<BR>
	 * in a given line identified by a pattern
	 * @param filePath -file path to patch
	 * @param LinePattern - pattern to recognize specific line by
	 * @param searchPattern - pattern which will be replaced
	 * @param newText - new string
	 * @return number of lines patched
	 */
	public int replaceAllInLine(String filePath, String LinePattern,String searchPattern, String newText)
	{
		StringProccessMethod method=new replaceAll();
		return PatchFile(filePath,LinePattern,searchPattern,newText,method);
	}
	
	/**function gets as input a file to patch and patch given line using supplied method.  
	 * 
	 * @param filePath - relative or absolute path of file to patch
	 * @param linePattern - pattern to recognize the line needed to patch. enter ".*" to perform action on entire file
	 * @param searchPattern - pattern to find inside the line
	 * @param newText - new text to use
	 * @param method - method to perform on given line
	 * @return function return a counter to the amount of lines patched
	 */
	private int PatchFile(String filePath, String linePattern, String searchPattern, String newText, StringProccessMethod method)
	{
		int count=0;
		BufferedReader br=null;
		BufferedWriter bw=null;
		int lineNumber=0;
		int lines=0;
		
		try {
			lineNumber=Integer.parseInt(linePattern);
			if (lineNumber<0)
			{
				lineNumber=0;
				Log.debug("invalid line number: "+linePattern);
			}
			else
				Log.debug("patching line number: "+linePattern);
		} catch (NumberFormatException e) {}
		
		String EOL=System.getProperty("line.seperator");
		Log.debug("Patching file: "+filePath+". Method: "+method.getMethodName()+". Line Pattern: "+linePattern+". searchPattern: "+searchPattern+". New text: "+newText);

		//read EOL characters from the file. and keep it when copying the file
		BufferedReader reader=null; 
		try
		{
			reader= new BufferedReader(new FileReader(filePath));
			char[] buf = new char[1024];
			int numRead = 0;
			numRead = reader.read(buf);
			if (numRead != -1)
			{
				String readData = String.valueOf(buf, 0, numRead);
				int ind = readData.indexOf("\n");
				if (ind==-1)
					EOL="\r";
				else if (readData.charAt(ind - 1) == '\r')
					EOL="\r\n";
				else
					EOL="\n";
			}
			reader.close();

			//open file to read and write
			bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath+TEMP_TAG),"UTF8"));
			br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"utf-8"));

			String strLine;
			String patchedLine;

			//Read File Line By Line
			while ((strLine = br.readLine()) != null)
			{
				lines++;
				if (linePattern==null)
				{
					patchedLine=method.replace(strLine, searchPattern, newText, EOL);
					if (strLine.compareTo(patchedLine)!=0)
					{
						Log.debug("new Line: " + patchedLine);
						count++;
					}
					bw.write(patchedLine+EOL);
					bw.flush();
				}
				else if (strLine.matches(linePattern) || lines==lineNumber)
				{
					count++;
					patchedLine=method.replace(strLine, searchPattern, newText, EOL);
					if (patchedLine==null) //if line was deleted
						continue;
					Log.debug("new Line: " + patchedLine);
					bw.write(patchedLine+EOL);
					bw.flush();
				}
				else
				{
					bw.write(strLine+EOL);
					bw.flush();
				}
			}

			br.close();
			bw.close();
			
			File f=new File(filePath);
			if (!f.exists())
				return -1;
			if (f.delete())
				new File(filePath+TEMP_TAG).renameTo(new File(filePath));
			else
			{
				Log.error("could not delete original file");
				return -1;
			}
			if (count==0)
			{
				Log.debug("no change was made");
				return count;
			}
		}
		catch (FileNotFoundException e)
		{
			Log.writeEx(e);
			return -1;
		} catch (IOException e)
		{
			Log.writeEx(e);
			return -1;
		} catch (PatternSyntaxException e) {
			Log.writeEx(e);
			return -1;
		}
		finally
		{
			try {
				reader.close();
				br.close();
				bw.close();
			} catch (Exception e2)
			{
				Log.writeEx(e2);
			}
		}
		return count;
	}
}